#include "StdAfx.h"
#include "MTPconnect.h"


MTPconnect::MTPconnect(void)
{
    hr = S_OK;

    hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceKeyCollection,
                          (VOID**)&keys);
    // http://msdn.microsoft.com/en-us/library/ff597893%28v=VS.85%29.aspx
    hr = keys->Add(WPD_OBJECT_NAME);
    hr = keys->Add(WPD_OBJECT_SIZE);
    hr = keys->Add(WPD_OBJECT_CONTENT_TYPE);
    hr = keys->Add(WPD_OBJECT_FORMAT);
    hr = keys->Add(WPD_OBJECT_PARENT_ID);
    hr = keys->Add(WPD_OBJECT_ORIGINAL_FILE_NAME);

    devcb = NULL;
}

MTPconnect::~MTPconnect(void)
{
    if (devcb != NULL)
    {
        devcb->UnRegister(device);
        devcb->Release();
    }
}

/**
    Find and connect to the device if possible
    */
MTPconnect::ERRORCODE MTPconnect::connect()
{
    hr = S_OK;
    LPWSTR path;
    path = getDevPath();
    if (path != NULL)
    {
        printf("%d Attempting to connect to device %ws\n", M_INFO, path);
        hr = connectToDev(path);
        if (SUCCEEDED(hr))
        {
            printf("%d Successfully connected to device %ws\n", M_INFO, path);

            // register for callbacks
            devcb = new PortDevEventCallback;
            hr = devcb->Register(device);

            return CONNECT_SUCCESS;
        }
        else
        {
            return CONNECT_FAIL;
        }
    }
    else
    {
        return CONNECT_FAIL;
    }
}

/**
    Loop through all connected MTP devices and return the path to the Transformer TF101
    */
LPWSTR MTPconnect::getDevPath()
{
    hr = S_OK;
    LPWSTR* devList = NULL;
    DWORD devCount = 0;

    hr = CoCreateInstance(CLSID_PortableDeviceManager,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceManager,
                          (VOID**) &devMgr);
    if (FAILED(hr))
    {
        printf("%d CoCreateInstance() failed: 0x%lx\n", M_ERROR, hr);
        return NULL;
    }
    // Get a count of the devices available by passing
    // a NULL buffer
    hr = devMgr->GetDevices(NULL, &devCount);

    // Allocate enough memory for the device path names
    devList = new LPTSTR[devCount];

    // Get the list of device paths using the valid buffer
    hr = devMgr->GetDevices(devList, &devCount);

    WCHAR *name;
    DWORD namelen = 0;

    for (unsigned int k = 0; k < devCount; k++)
    {
        devMgr->GetDeviceDescription(devList[k], NULL, &namelen);
        name = new WCHAR[namelen];
        devMgr->GetDeviceDescription(devList[k], name, &namelen);
        //wprintf(L"name: %s\n", name);
        if (wcscmp(name, DEVICE_DESCRIPTION) == 0)
        {
            return devList[k];
        }
    }
    return NULL;
}

/**
    Connect to the MTP device given by path
    */
HRESULT MTPconnect::connectToDev(LPWSTR path)
{
    hr = S_OK;

    // Create our client information collection
    hr = CoCreateInstance(CLSID_PortableDeviceValues, 
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceValues,
                          (VOID**)&devInfo);

    // We have to provide at the least our name, version, revision
    hr = devInfo->SetStringValue(WPD_CLIENT_NAME, CLIENT_NAME);
    hr = devInfo->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 1);
    hr = devInfo->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
    hr = devInfo->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 1);

    // A WPD device is represented by an IPortableDevice instance
    hr = CoCreateInstance(CLSID_PortableDevice, 
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDevice,
                          (VOID**)&device);

    // We are now ready to open a connection to the device
    // We'll assume cdwDevices>0 and connect to the first device path
    hr = device->Open(path, devInfo);
    return hr;
}

/**
    Set the root directory for ARTour files. THIS SHOULD ONLY BE CALLED ONCE.
    For now, it just finds dir under /sdcard (aka 'Internal Storage') (/sdcard/${dir})
    dir - name of the new (or existing) directory
    returns - object id of the directory
    */
LPWSTR MTPconnect::setDir(LPCWSTR dir)
{
    device->Content(&devContent);
    hr = devContent->Properties(&devProp);

    // The root object on the asus transformer only has one child: 'Internal Storage'. Every other
    // directory is under this oject. The below code just grabs the first child and assumes it is correct.
    // TODO: make this less dangerous / usable on more devices.
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, WPD_DEVICE_OBJECT_ID, NULL, &children);

    LPWSTR objID = NULL;
    ULONG numFetched = 0;
    hr = children->Next(1, &objID, &numFetched);
    artourFolderID = createDir(objID, L"artour");

    return artourFolderID;
}

/**
    Create a directory under the given parent object id. If the directory already
    exists, just return it's object id
    */
LPWSTR MTPconnect::createDir(LPWSTR parentID, LPWSTR folderName)
{
    // Iterate through parentID's children to see if folderName exists already
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, parentID, NULL, &children);
    while (hr == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        hr = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hr = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            if (wcsicmp(folderName, name) == 0)
            {
                // The folder exists, return the id
                // TODO: should probably ensure it is actually a folder
                //printf("%d Using existing folder \"%ws\"\n", M_INFO, folderName);
                return objID;
            }
        }
    }

    // The folder does not exist, create it
    CComPtr<IPortableDeviceValues> values;
    hr = CoCreateInstance(CLSID_PortableDeviceValues,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceValues,
                          (VOID**)&values);

    values->SetStringValue(WPD_CLIENT_NAME, CLIENT_NAME);
    hr = values->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 1);
    hr = values->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
    hr = values->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 1);
    hr = values->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_FOLDER);
    hr = values->SetStringValue(WPD_OBJECT_PARENT_ID, parentID);
    hr = values->SetStringValue(WPD_OBJECT_NAME, folderName);
    hr = values->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_PROPERTIES_ONLY);
    hr = values->SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, folderName);

    LPWSTR id = NULL;
    hr = devContent->CreateObjectWithPropertiesOnly(values, &id);
    printf("%d Created new folder \"%ws\", id = %ws\n", M_INFO, folderName, id);

    return id;
}

void MTPconnect::getRootFolder(LPWSTR rootID, LPWSTR localPath)
{
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    HRESULT hres = devContent->EnumObjects(0, rootID, NULL, &children);
    WCHAR *objPath = new WCHAR[MAX_PATH];
    while (hres == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        GUID objType;
        hres = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hres = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            hres = childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            hres = childVals->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, &objType);

			// get the local path for the object (localPath + object name)
            wcscpy(objPath, localPath);
            wcscat(objPath, L"\\");
            wcscat(objPath, name);
            if (objType == WPD_CONTENT_TYPE_FOLDER)
            {
				printf("%d Found folder \"%ws\"\n", M_INFO, name);
                fflush(stdout);
				if (_wmkdir(objPath) == -1)
				{
					if (errno == ENOENT)
					{
						printf("%d Folder not created, path \"%ws\" not found (attempting to continue)\n", M_ERROR, objPath);
                        fflush(stdout);
						continue;
					}
				}
                getRootFolder(objID, objPath);
            }
            else
            {
				printf("%d Found item \"%ws\", copying to local folder\n", M_INFO, name);
                fflush(stdout);
                getObject(objID, objPath);
            }
        }
    }
    delete objPath;
}

WCHAR * MTPconnect::getObject(LPWSTR objID, LPWSTR localPath)
{
    CComPtr<IPortableDeviceResources> devRes;
    DWORD optTransSize = 0;
    CComPtr<IStream> objDataStream;
    CComPtr<IStream> finalFileStream;

    hr = devContent->Transfer(&devRes);
    if (FAILED(hr))
    {
        printf("%d Failed to get IPortableDeviceResources, hr = 0x%lx\n", M_ERROR, hr);
        return NULL;
    }

    hr = devRes->GetStream(objID, WPD_RESOURCE_DEFAULT, STGM_READ, &optTransSize, &objDataStream);
    if (FAILED(hr))
    {
        printf("%d Failed to get IStream from IPortableDeviceResources, hr = 0x%lx\n", M_ERROR, hr);
        return NULL;
    }

    hr = SHCreateStreamOnFile(localPath, STGM_CREATE | STGM_WRITE, &finalFileStream);
    if (FAILED(hr))
    {
        printf("%d Failed to create tmp file (%ws) to transfer object (%ws), hr = 0x%lx\n", M_ERROR, localPath, objID, hr);
        return NULL;
    }

    hr = copyStream(objDataStream, finalFileStream, optTransSize, NULL);
    if (FAILED(hr))
    {
        printf("%d Failed to copy file \"%ws\" on device to tmp file (%ws), hr = 0x%lx\n", M_ERROR, objID, localPath, hr);
        return NULL;
    }
    finalFileStream->Commit(0);

    return localPath;
}

/**
    Find file under the given folder, copy it to the local machine, and return the path to
    the file on the local machine.
    parentID - id of the folder to scan
    return - WCHAR* containing file location or NULL. Defaults to "artour.xml". Caller should de-allocate.
    */
WCHAR * MTPconnect::getFile(LPWSTR parentID, LPWSTR fileName)
{
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, parentID, NULL, &children);
    while (hr == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        hr = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hr = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            if (wcsicmp(fileName, name) == 0)
            {
                // fileName exists, copy it to the local machine;
                CComPtr<IPortableDeviceResources> devRes;
                DWORD optTransSize = 0;
                CComPtr<IStream> objDataStream;
                CComPtr<IStream> finalFileStream;

                hr = devContent->Transfer(&devRes);
                if (FAILED(hr))
                {
                    printf("%d Failed to get IPortableDeviceResources, hr = 0x%lx\n", M_ERROR, hr);
                    return NULL;
                }

                hr = devRes->GetStream(objID, WPD_RESOURCE_DEFAULT, STGM_READ, &optTransSize, &objDataStream);
                if (FAILED(hr))
                {
                    printf("%d Failed to get IStream from IPortableDeviceResources, hr = 0x%lx\n", M_ERROR, hr);
                    return NULL;
                }

                WCHAR *tmpFileName = getTmpFileName();
                hr = SHCreateStreamOnFile(tmpFileName, STGM_CREATE | STGM_WRITE, &finalFileStream);
                if (FAILED(hr))
                {
                    printf("%d Failed to create tmp file (%ws) to transfer object (%ws), hr = 0x%lx\n", M_ERROR, tmpFileName, objID, hr);
                    return NULL;
                }

                hr = copyStream(objDataStream, finalFileStream, optTransSize, NULL);
                if (FAILED(hr))
                {
                    printf("%d Failed to copy file \"%ws\" on device to tmp file (%ws), hr = 0x%lx\n", M_ERROR, name, tmpFileName, hr);
                    return NULL;
                }
                finalFileStream->Commit(0);

                return tmpFileName;
            }
        }
    }
    // fileName does not exist
    return NULL;
}

/**
    Set the properties for an object.
    ft - File type of the object
    parentFolderID - object id of the parent folder
    path - path to the file on the local machine
    fileStream - stream for the file specified by path
    [out] values - IPortableDeviceValues for the file specified by path and fileStream
    */
MTPconnect::ERRORCODE MTPconnect::getProperties(FILE_TYPE ft, LPWSTR parentFolderID, LPWSTR path, IStream *fileStream, IPortableDeviceValues **values)
{
    CComPtr<IPortableDeviceValues> tmpVals;

    hr = CoCreateInstance(CLSID_PortableDeviceValues, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&tmpVals));
    if (FAILED(hr))
    {
        printf("%d Failed to create property information, hr = 0x%lx\n", M_ERROR, hr);
        return PROPERTY_CREATE_FAIL;
    }

    hr = tmpVals->SetStringValue(WPD_OBJECT_PARENT_ID, parentFolderID);
    if (FAILED(hr))
    {
        printf("%d Failed to set parent id, hr = 0x%lx\n", M_ERROR, hr);
        return PROPERTY_CREATE_FAIL;
    }

    STATSTG stats = {0};
    hr = fileStream->Stat(&stats, STATFLAG_NONAME);
    if (FAILED(hr))
    {
        printf("%d Failed to get size for file (%ws), hr = 0x%lx\n", M_ERROR, path, hr);
        return FILE_OPEN_FAIL;
    }
    hr = tmpVals->SetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, stats.cbSize.QuadPart);
    if (FAILED(hr))
    {
        printf("%d Failed to set file size, hr = 0x%lx\n", M_ERROR, hr);
        return PROPERTY_CREATE_FAIL;
    }

    // There should only be one xml file. The name should always be "artour.xml" (TODO: is it?)
    if (ft == FT_XML)
    {
        hr = tmpVals->SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, L"artour.xml");
        if (FAILED(hr))
        {
            printf("%d Failed to set object original file name, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetStringValue(WPD_OBJECT_NAME, L"artour.xml");
        if (FAILED(hr))
        {
            printf("%d Failed to set object name, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
    }
    else
    {
        LPWSTR fname = getFileNameFromPath(path);
        hr = tmpVals->SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, fname);
        if (FAILED(hr))
        {
            printf("%d Failed to set object original file name, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetStringValue(WPD_OBJECT_NAME, fname);
        if (FAILED(hr))
        {
            printf("%d Failed to set object name, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
    }

    // Filetype specific properties
    switch (ft)
    {
    case FT_PIC:
        // TODO: anything besides jpg?
        hr = tmpVals->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_IMAGE);
        if (FAILED(hr))
        {
            printf("%d Failed to set content type, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_EXIF);
        if (FAILED(hr))
        {
            printf("%d Failed to set object format, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        break;
    case FT_VID:
        // TODO: anything besides mp4?
        hr = tmpVals->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_VIDEO);
        if (FAILED(hr))
        {
            printf("%d Failed to set content type, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_MP4);
        if (FAILED(hr))
        {
            printf("%d Failed to set object format, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        break;
    case FT_AUD:
        // TODO: anything besides mp3?
        hr = tmpVals->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_AUDIO);
        if (FAILED(hr))
        {
            printf("%d Failed to set content type, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_MP3);
        if (FAILED(hr))
        {
            printf("%d Failed to set object format, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        break;
    case FT_XML:
        hr = tmpVals->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_DOCUMENT);
        if (FAILED(hr))
        {
            printf("%d Failed to set content type, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_XML);
        if (FAILED(hr))
        {
            printf("%d Failed to set object format, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        break;
    case FT_TXT:
        hr = tmpVals->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, WPD_CONTENT_TYPE_DOCUMENT);
        if (FAILED(hr))
        {
            printf("%d Failed to set content type, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        hr = tmpVals->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_TEXT);
        if (FAILED(hr))
        {
            printf("%d Failed to set object format, hr = 0x%lx\n", M_ERROR, hr);
            return PROPERTY_CREATE_FAIL;
        }
        break;
    default:
        // should never happen
        printf("%d File type not recognized: %ws\n", M_ERROR, path);
        return PROPERTY_CREATE_FAIL;
    }

    hr = tmpVals->QueryInterface(IID_PPV_ARGS(values));
    if (FAILED(hr))
    {
        printf("%d QueryInterface() failed for IPortableDeviceValues, hr = 0x%lx\n", M_ERROR, hr);
        return PROPERTY_CREATE_FAIL;
    }
    return SUCCESS;
}

/**
    Write a file to the device.
    parentFolderID - object id of the parent folder
    path - path to the file on the local system
    ft - file type
    */
MTPconnect::ERRORCODE MTPconnect::writeFile(LPWSTR parentFolderID, LPWSTR path, FILE_TYPE ft)
{
    hr = S_OK;
    DWORD bufferSize = 0;
    CComPtr<IStream> fileStream;
    CComPtr<IStream> devTmpStream;
    CComPtr<IPortableDeviceDataStream> deviceDataStream;
    CComPtr<IPortableDeviceValues> values;

    hr = SHCreateStreamOnFileEx(path, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, NULL, &fileStream);
    if (FAILED(hr))
    {
        printf("%d Failed to open file (%ws), hr = 0x%lx\n", M_ERROR, path, hr);
        return FILE_OPEN_FAIL;
    }

    if (getProperties(ft, parentFolderID, path, fileStream, &values) != SUCCESS)
    {
        printf("%d Failed to set object properties. It will not be copied.\n", M_ERROR);
        return PROPERTY_CREATE_FAIL;
    }

    hr = devContent->CreateObjectWithPropertiesAndData(values, &devTmpStream, &bufferSize, NULL);
    if (FAILED(hr))
    {
        printf("%d CreateObjectWithPropertiesAndData() failed, hr = 0x%lx\n", M_ERROR, hr);
        return UNSPEC_ERROR;
    }

    hr = devTmpStream->QueryInterface(IID_PPV_ARGS(&deviceDataStream));
    if (FAILED(hr))
    {
        printf("%d QueryInterface() failed, hr = 0x%lx\n", M_ERROR, hr);
        return UNSPEC_ERROR;
    }

    // Copy from the file stream to the device stream
    hr = copyStream(fileStream, deviceDataStream, bufferSize, NULL);
    if (FAILED(hr))
    {
        printf("%d Failed to copy file \"%ws\" to device. The file probably already exists, hr = 0x%lx\n", M_ERROR, path, hr);
        return UNSPEC_ERROR;
    }

    hr = deviceDataStream->Commit(0);
    if (FAILED(hr))
    {
        printf("%d Failed to commit object to device, hr = 0x%lx\n", M_ERROR, hr);
        return UNSPEC_ERROR;
    }
    return SUCCESS;
}

/**
    Given a complete path, return just the filename (including extension)
    */
LPWSTR MTPconnect::getFileNameFromPath(LPWSTR path)
{
    WCHAR name[MAX_PATH] = {0};
    WCHAR ext[MAX_PATH] = {0};
    _wsplitpath_s(path, NULL, 0, NULL, 0, name, ARRAYSIZE(name), ext, ARRAYSIZE(ext));
    int size = (wcslen(name) + wcslen(ext)) * sizeof(wchar_t);
    WCHAR *fname = new WCHAR[size];
    wcscpy(fname, name);
    wcscat(fname, ext);
    return fname;
}

/**
    Gets a temporary file name. Attempts to use the temp directory and a file name with no conflicts.
    */
WCHAR * MTPconnect::getTmpFileName()
{
    WCHAR tmpPathBuffer[MAX_PATH];
    WCHAR *tmpFileName = new WCHAR[MAX_PATH];
    DWORD dwRetVal = 0;
    UINT uiRetVal = 0;

    dwRetVal = GetTempPath(MAX_PATH, tmpPathBuffer);
    if (dwRetVal > MAX_PATH || dwRetVal == 0)
    {
        printf("%d GetTempPath() failed, attempting to use current directory\n", M_ERROR);
        wcscpy(tmpPathBuffer, L".\\");
    }

    uiRetVal = GetTempFileName(tmpPathBuffer, L"artour", 0, tmpFileName);
    if (uiRetVal == 0)
    {
        printf("%d GetTempFileName() failed, attempting to fall back to %wsartour.tmp\n", M_ERROR, tmpPathBuffer);
        wcscpy(tmpFileName, tmpPathBuffer);
        wcscat(tmpFileName, L"artour.tmp");
    }

    return tmpFileName;
}

/**
    Copy an input stream to an output stream.
    input - the input stream
    output - the output stream
    bufSize - size of the buffer used for copy
    [out] bytesWritten - Stores total bytes written. Ignored if NULL.
    */
HRESULT MTPconnect::copyStream(IStream *input, IStream *output, DWORD bufSize, DWORD *bytesWritten)
{
    HRESULT hres = S_OK;
    DWORD bytesRead = 0;
    DWORD bytesWr = 0;
    DWORD totalWritten = 0;
    BYTE *buffer = new (std::nothrow) BYTE[bufSize];
    do
    {
        hres = input->Read(buffer, bufSize, &bytesRead);
        if (FAILED(hres))
        {
            printf("%d Failed to read from source stream, hr = 0x%lx\n", M_ERROR, hres);
            delete [] buffer;
            return hres;
        }
        hres = output->Write(buffer, bytesRead, &bytesWr);
        if (FAILED(hres))
        {
            printf("%d Failed to write to destination stream, hr = 0x%lx\n", M_ERROR, hres);
            delete [] buffer;
            return hres;
        }
        totalWritten += bytesWr;
    } while ((SUCCEEDED(hres)) && (bytesRead > 0));
    delete [] buffer;

    if (bytesWritten != NULL)
    {
        *bytesWritten = totalWritten;
    }
    return hres;
}

/**
    Check if a file exists on the device.
    parentID - Object id of the parent folder
    fileName - Name of the file to look for
    returns - true if the file exists, false otherwise
    */
bool MTPconnect::checkExists(LPWSTR parentID, LPWSTR fileName)
{
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, parentID, NULL, &children);
    while (hr == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        hr = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hr = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            if (wcsicmp(fileName, name) == 0)
            {
                return true;
            }
        }
    }
    return false;
}

MTPconnect::ERRORCODE MTPconnect::deleteObj(LPWSTR parentID, LPWSTR objName, bool recursive)
{
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, parentID, NULL, &children);
    while (hr == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        hr = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hr = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            if (wcsicmp(objName, name) == 0)
            {
                CComPtr<IPortableDevicePropVariantCollection> toDelete;
                hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection,
                                      NULL,
                                      CLSCTX_INPROC_SERVER,
                                      IID_PPV_ARGS(&toDelete));
                if (FAILED(hr))
                {
                    printf("%d Filed to create PropVariantCollection, hr = 0x%lx\n", M_ERROR, hr);
                    return UNSPEC_ERROR;
                }

                PROPVARIANT pv = {0};
                PropVariantInit(&pv);
                pv.vt = VT_LPWSTR;
                pv.pwszVal = AtlAllocTaskWideString(objID);

                hr = toDelete->Add(&pv);
                if (FAILED(hr))
                {
                    printf("%d Failed to add PROPVARIANT to PropVariantCollection, hr = 0x%lx\n", M_ERROR, hr);
                    return UNSPEC_ERROR;
                }
                if (recursive)
                    hr = devContent->Delete(PORTABLE_DEVICE_DELETE_WITH_RECURSION, toDelete, NULL);
                else
                    hr = devContent->Delete(PORTABLE_DEVICE_DELETE_NO_RECURSION, toDelete, NULL);
                if (FAILED(hr))
                {
                    printf("%d Failed to delete object (%ws), hr = 0x%lx\n", M_ERROR, objName, hr);
                    return UNSPEC_ERROR;
                }

                PropVariantClear(&pv);
                return SUCCESS;
            }
        }
    }
    return NO_FILE_ERROR;
}

/**
    Renames a folder object
    parentID - objid of the parent folder
    objName - old folder name
    newObjName - new folder name
    */
MTPconnect::ERRORCODE MTPconnect::renameFolderObj(LPWSTR parentID, LPWSTR objName, LPWSTR newObjName)
{
    CComPtr<IEnumPortableDeviceObjectIDs> children;
    hr = devContent->EnumObjects(0, parentID, NULL, &children);
    while(hr == S_OK)
    {
        LPWSTR objID = NULL;
        ULONG count = 0;
        CComHeapPtr<WCHAR> name;
        hr = children->Next(1, &objID, &count);
        CComPtr<IPortableDeviceValues> childVals;
        hr = devProp->GetValues(objID, keys, &childVals);
        if (childVals != NULL)
        {
            childVals->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &name);
            if (wcscmp(objName, name) == 0)
            {
                CComPtr<IPortableDeviceValues> newChildVals;
                CComPtr<IPortableDeviceValues> result;
                hr = CoCreateInstance(CLSID_PortableDeviceValues,
                                      NULL,
                                      CLSCTX_INPROC_SERVER,
                                      IID_IPortableDeviceValues,
                                      (VOID**)&newChildVals);
                newChildVals->SetStringValue(WPD_OBJECT_NAME, newObjName);
                newChildVals->SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, newObjName);
                hr = devProp->SetValues(objID, newChildVals, &result);
                if (hr != S_OK)
                {
                    // WPD_OBJECT_NAME seems to have issues sometimes, but the rename operation still seems to complete successfully.
                    // No need to tell the user something went wrong in that case.
                    //printf("%d Failed to set one or more property values in renameFolderObj(), hr = 0x%lx\n", M_ERROR, hr);
                    HRESULT n, nn;
                    result->GetErrorValue(WPD_OBJECT_NAME, &n);
                    result->GetErrorValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &nn);
                    //printf("%d objname: 0x%lx, objorigname: 0x%lx\n", M_INFO, n, nn);
                    return UNSPEC_ERROR;
                }
                return SUCCESS;
            }
        }
    }
}